﻿using Nemerle.Collections;
using Nemerle.Text;
using Nemerle.Utility;

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.IO;
using System.Windows.Forms;

using ICSharpCode.TextEditor.Document;
using ICSharpCode.TextEditor;
using System.Reflection;
using NPad.Configuration;

namespace NPad
{
  /// <summary>
  /// Description of MainForm.
  /// </summary>
  public partial class MainForm : Form
  {
    defaultExtension = ".n";
    
    isDirty : bool 
    { 
      mutable _isDirty : bool;
      get {_isDirty}; 
      set {_isDirty = value; updateTitle()} 
    }

    currentFileName : string 
    { 
      mutable _currentFileName : string;
      get {_currentFileName}; 
      set {_currentFileName = value; updateTitle()} 
    }
    
    public this()
    {
      InitializeComponent();

      initCompiler();
      initEditor();
      initFileBrowser();
      initToolbarAndMenu();
    }

    #region Event system
    subscribers : Dictionary[object, List[ToolStripItem]] = Dictionary();
    subscribe(item : ToolStripItem, action : void -> void) : void
    {
      item.Click += fun(_, _) { action() } 

      if (subscribers.ContainsKey(action))
        subscribers[action].Add(item)
      else
        subscribers[action] = List([item])
    }
    
    subscribe(items : IEnumerable[ToolStripItem], action : void -> void) : void
    {
      items.Iter(subscribe(_, action))
    }
    #endregion
    
    initToolbarAndMenu() : void 
    {
      subscribe([newToolStripButton, newToolStripMenuItem], newFile);
      subscribe([openToolStripButton, openToolStripMenuItem], openFile);
      subscribe([saveToolStripButton, saveToolStripMenuItem], saveFile);
      subscribe(saveAsToolStripMenuItem, saveFileAs);
      subscribe(exitToolStripMenuItem, exit);
      
      Closing += (_, args) =>
      {
        args.Cancel = true;
        trySaveBeforeAction(() => {args.Cancel = false;})
      }

      subscribe([runToolStripMenuItem, runToolStripButton], runCode);

      subscribe(undoToolStripMenuItem, constructEditorAction(Actions.Undo()));
      subscribe(redoToolStripMenuItem, constructEditorAction(Actions.Redo()));
      subscribe([cutToolStripButton, cutToolStripMenuItem], constructEditorAction(Actions.Cut()));
      subscribe([copyToolStripButton, copyToolStripMenuItem], constructEditorAction(Actions.Copy()));
      subscribe([pasteToolStripButton, pasteToolStripMenuItem], constructEditorAction(Actions.Paste()));
      
      subscribe(settingsMenuItem, showSettingsDialog);
     
      aboutToolStripMenuItem.Enabled = false;
    }
    
    initCompiler() : void 
    {
      when (String.IsNullOrEmpty(Cfg.Instance.NemerlePath))
          _ = MessageBox.Show(
                "Nemerle not found. Set Nemerle environment variable or install nemerle into Program Files\\Nemerle");
    }
    
    private initEditor() : void
    {
        codeEditor.Document.HighlightingStrategy = GetHighlightingStrategy();
        codeEditor.Document.DocumentChanged += (_, _) =>
        {
            isDirty = true;
        };
        
        match (Environment.GetCommandLineArgs().ToNList())
        {
          | [_, file] when File.Exists(file) => loadFile(Path.GetFullPath(file));
          | [_, file] when file.IndexOfAny(Path.GetInvalidPathChars()) == -1 
            => 
              def file = if (String.IsNullOrEmpty(Path.GetExtension(file)))
                $"$file$defaultExtension"
              else
                file;
                
              newFile();
              currentFileName = Path.GetFullPath(file);
          | [_] => ();
          | _ :: args => _ = MessageBox.Show($"Invalid args: ..$args\nUsage: npad filename");
        }
        
    }

    private initFileBrowser() : void
    {
        def snippetsCollection = SnippetCollection("Samples", "Samples");

        fileBrowser.Nodes.Clear();
        def collectionNode = fileBrowser.Nodes.Add(snippetsCollection.Title);
        def baseDirectory = AppDomain.CurrentDomain.BaseDirectory;

        snippetsCollection.ForEachFile(baseDirectory, file =>
        {
            def node = collectionNode.Nodes.Add(Path.GetFileNameWithoutExtension(file));
            node.ToolTipText = file;
            node.Tag = file;
        });

        collectionNode.Expand();
    }
  
    private fileBrowser_NodeMouseClick (_ : object,  e : System.Windows.Forms.TreeNodeMouseClickEventArgs) : void
    {
      match (e.Node.Tag :> string)
      {
        | null => ()
        | fileName => loadFile(fileName);
      }
    }
    
    loadFile(fileName : string) : void 
    {
        codeEditor.LoadFile(fileName);
        codeEditor.Document.HighlightingStrategy = GetHighlightingStrategy();
        currentFileName = fileName;
        isDirty = false;
    }

    updateTitle() : void 
    {
      def dirty = if (isDirty) "*" else "";
      
      def (fileName, separator) = match (currentFileName)
      {
        | null => ("", "")
        | path => (Path.GetFileName(path), " - ")
      }
      
      Text = $"$(dirty)$(fileName)$(separator)Nemerle Pad";
    }
    
    GetHighlightingStrategy() : IHighlightingStrategy 
    {
      HighlightingStrategyFactory.CreateHighlightingStrategy("Nemerle");
    }
    
    
    #region Menu commands
    openFile() : void
    {
      def open() 
      {
        match (openFileDialog1.ShowDialog())
        {
          | OK      => loadFile(openFileDialog1.FileName);
          | Cancel  => ();
          | _       => throw ArgumentException()
        }
      }
      
      trySaveBeforeAction(open)
    }

    trySaveBeforeAction(action : void -> void) : void
    {
      if (isDirty)
      {
        def fileName = match (currentFileName)
        {
          | null => "File"
          | path => Path.GetFileName(path)
        }
        match (MessageBox.Show($"$fileName was changed, save?", "File changes may be lost", MessageBoxButtons.YesNoCancel))
        {
          | Yes => 
            saveFile();
            action();
          | No => 
            action();
          | Cancel => ();
          | _ => throw ArgumentException()
        }
      }
      else
        action()
    }
    
    newFile() : void
    {
      def newFile()
      {
        // isnt clear editor api?
        doEditorAction(Actions.SelectWholeDocument());
        doEditorAction(Actions.Delete());
        codeEditor.Document.HighlightingStrategy = GetHighlightingStrategy();

        currentFileName = null;
        isDirty = false;
      }
      
      trySaveBeforeAction(newFile)
    }

    saveFile() : void
    {
      if (currentFileName == null)
        saveFileAs()
      else
      {
        codeEditor.SaveFile(currentFileName);
        isDirty = false;
      }
    }

    saveFileAs() : void
    {
      saveFileDialog1.FileName = match (currentFileName)
      {
        | null => ""
        | name => name
      }
        
      match (saveFileDialog1.ShowDialog())
      {
        | OK => 
          codeEditor.SaveFile(saveFileDialog1.FileName);
          currentFileName = saveFileDialog1.FileName;
          isDirty = false;
        | Cancel => ();
        | _ => throw ArgumentException()
      }
    }

    exit() : void
    {
      trySaveBeforeAction(Close);
    }

    constructEditorAction(action : Actions.IEditAction) : void -> void
    {
      () => doEditorAction(action)
    }
    
    doEditorAction(action : Actions.IEditAction) : void
    {
        def editor = codeEditor;
        def area = editor.ActiveTextAreaControl.TextArea;
        editor.BeginUpdate();
			
        try 
        {
	        lock (editor.Document) 
	        {
		        action.Execute(area);
		        when (area.SelectionManager.HasSomethingSelected && area.AutoClearSelection /*&& caretchanged*/) 
		        {
			        when (area.Document.TextEditorProperties.DocumentSelectionMode == DocumentSelectionMode.Normal) 
			        {
				        area.SelectionManager.ClearSelection();
			        }
		        }
	        }
        } 
        finally 
        {
	        editor.EndUpdate();
	        area.Caret.UpdateCaretPosition();
        }
    }
    
    showSettingsDialog() : void
    {
      using (dialog = SettingsForm())
      {
        _ = dialog.ShowDialog();
      }
    }
    #endregion
    
    runCode() : void
    {
      def fileName = Path.GetTempFileName();
      
      def assemblyFullName = Assembly.GetExecutingAssembly().FullName;
      
      def domain = if (Cfg.Instance.UseSeparateDomain) 
                     AppDomain.CreateDomain(assemblyFullName)
                   else
                     null;
                     
      outputTextBox.Text = "";
      outputTextBox.Update();
      
      try
      {
        codeEditor.SaveFile(fileName);
        
        def runner = match (domain)
        { 
          | null => AppRunner()
          | some => 
              some.CreateInstanceAndUnwrap(assemblyFullName, typeof(AppRunner).FullName) :> AppRunner;
        }
        
        runner.Setup(Cfg.Instance.NemerlePath, Cfg.Instance.CompilerType);

        def result = runner.CompileAndRun(FileInfo(fileName));
        
        def logFileName = if (String.IsNullOrEmpty(currentFileName))
            "snippet.n";
          else
            Path.GetFileName(currentFileName);
        
        def escapedName = Regex.Escape(fileName);

        def result = Regex
          .Replace(result, $"^$(escapedName)", logFileName, RegexOptions.Multiline);
          
        outputTextBox.Text = result;
        outputTextBox.SelectionStart = outputTextBox.Text.Length;
        _ = outputTextBox.ScrollToCaret;
      }
      catch
      {
        | e =>
          outputTextBox.Text = e.ToString()
      }
      finally
      {
        when (domain != null) AppDomain.Unload(domain);
        File.Delete(fileName)
      }
    }
  }
}
